package org.walkerljl.commons.designer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class TaskExecuteEngine<Event> {

    private static final List<Future<?>> EMPTY_FUTURES = new ArrayList<Future<?>>(0);
    private final Event event;
    private final ExecutorService executorService;
    private final CountDownLatch completeLatch;
    private final Map<EventHandler<Event>, Task<Event>> eventHandlerTaskMap;
    private final Callback<Event> callback;
    private final long timeout;
    private final Lock lock = new ReentrantLock();
    private final List<Future<?>> futures;
    private volatile Throwable error;
    private volatile boolean canceled = false;

    TaskExecuteEngine(Event event, ExecutorService executorService,
                      Map<EventHandler<Event>, Task<Event>> eventHandlerTaskMap,
                      Callback<Event> callback, long timeout) {
        this.event = event;
        this.executorService = executorService;
        this.eventHandlerTaskMap = eventHandlerTaskMap;
        this.callback = callback;
        if (callback == null) {
            completeLatch = new CountDownLatch(1);
        } else {
            completeLatch = null;
        }
        this.timeout = timeout;
        if (this.timeout > 0) {
            futures = new ArrayList<Future<?>>(1);
        } else {
            futures = EMPTY_FUTURES;
        }
    }

    Event run() {
        List<Task<Event>> noDependingTasks = getNoDependingTasks();
        for (Task<Event> task : noDependingTasks) {
            executeTask(task);
        }
        waitIfNecessary();
        return event;
    }

    private List<Task<Event>> getNoDependingTasks() {
        List<Task<Event>> noDependingTasks = new ArrayList<Task<Event>>(1);
        for (Task<Event> task : eventHandlerTaskMap.values()) {
            if (!task.hasUnsatisfiedDependcies()) {
                noDependingTasks.add(task);
            }
        }
        return noDependingTasks;
    }

    void markAsCompleted() {
        if (callback == null) {
            completeLatch.countDown();
        } else {
            callback.onSuccess(event);
        }
    }

    void markAsError(Throwable error) {
        if (callback == null) {
            this.error = error;
            completeLatch.countDown();
        } else {
            callback.onError(event, error);
        }
    }

    private void cancel() {
        lock.lock();
        try {
            canceled = true;
            for (Future<?> future : futures) {
                future.cancel(true);
            }
        } finally {
            lock.unlock();
        }
    }

    private void waitIfNecessary() {
        if (callback == null) {
            try {
                if (timeout > 0) {
                    if (!completeLatch.await(timeout, TimeUnit.MILLISECONDS)) {
                        cancel();
                        throw new TimeoutException();
                    }
                } else {
                    completeLatch.await();
                }

                if (error != null) {
                    throw new DesignerException(error);
                }
            } catch (InterruptedException e) {
                throw new DesignerException(e);
            }
        }
    }

    Callback<Event> getCallback() {
        return callback;
    }

    Task<Event> getTask(EventHandler<Event> eventHandler) {
        return eventHandlerTaskMap.get(eventHandler);
    }

    void executeTask(Task<Event> process) {
        if (timeout > 0) {
            lock.lock();
            try {
                if (!canceled) {
                    futures.add(executorService.submit(process));
                }
            } finally {
                lock.unlock();
            }
        } else {
            executorService.submit(process);
        }
    }
}
